package com.gosling.cloudspace.tools.poolexcel.excel;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.gosling.cloudspace.common.service.ExcelDataQueryService;
import com.gosling.cloudspace.config.ThreadPool;
import com.gosling.cloudspace.tools.IOTool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * @Description: easyexcel导出工具
 * @Author: qs
 * @Date: 2023/3/30 0030 13:42
 */

@Slf4j
public class EasyExcelPoolTool {

    /**
     * 控制各类数据导出执行状态
     */
    public static Map<String, Boolean> exportExcuteConcurrentMap = new ConcurrentHashMap<>();

    /**
     * excel多线程分片导出，按照文件分片，生成多个excel文件，打包为一个zip文件下载
     * @param queryService 分片数据查询service，需具体业务实现自己的查询逻辑；注意：实现分页查询的参数
     * @param head easyexcel导出数据类
     * @param exportKind 导出业务类型，控制不同业务导出执行状态
     * @param tempFolderPath 临时存放文件路径
     * @param fileName 文件名
     * @param sheetName sheet名称
     * @param downloadFileName 前端下载文件名称
     * @param dataCount 导出数据总大小
     * @param response 响应HttpServlet
     * @param <R> 分片查询数据返回类型
     */
    public static <R> void threadWriteShardingByFile(ExcelDataQueryService<R> queryService, Class head, String exportKind, String tempFolderPath
            , String fileName, String sheetName, String downloadFileName, long dataCount, HttpServletResponse response) throws Exception {

        long inTime = System.currentTimeMillis();

        if (Optional.ofNullable(exportExcuteConcurrentMap.get(exportKind)).orElse(Boolean.FALSE)) {
            throw new Exception("正在导出中，请勿重复发起");
        } else {
            exportExcuteConcurrentMap.put(exportKind, Boolean.TRUE);
        }

        // 一次查询条数，放在一个excel表中
        int areaSize = 50000;
        // 50万以内一次查询5w条，50-75w一次查询7.5w条，大于75w一次查询10w条
        if (dataCount > 500000 && dataCount <= 750000) {
            areaSize = 75000;
        } else if (dataCount > 750000) {
            areaSize = 100000;
        }
        // 计算分页数
        int pageCount = (int) (dataCount / areaSize) + 1;

        String today = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        final String folderPath = tempFolderPath + today + File.separator;
        File folder = new File(folderPath);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        long mkdirFinishTime = System.currentTimeMillis();
        log.info("创建文件夹完成：{} ms", mkdirFinishTime - inTime);

        CountDownLatch countDownLatch = new CountDownLatch(pageCount);
        ThreadPoolTaskExecutor pool =  new ThreadPool().taskExecutor();
        // 分页执行逻辑
        for (int i = 1; i <= pageCount; i++) {
            final int curPage = i;
            final int pageSize = areaSize;
            final long threadStartTime = System.currentTimeMillis();
            pool.execute(() -> {
                final long threadInTime = System.currentTimeMillis();
                log.info("线程开始执行，开启耗时：{}ms", threadInTime - threadStartTime);
                String filePath = folderPath + fileName + curPage + ".xlsx";
                ExcelWriter excelWriter = null;
                try {
                    excelWriter = EasyExcel.write(filePath, head).build();
                    WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
                    long excelFinishTime = System.currentTimeMillis();
                    log.info("excel文件创建完成：{} ms", excelFinishTime - threadInTime);

                    // 获取当前页导出数据
                    List<R> exportDataList = queryService.pageDataList(curPage, pageSize);
                    long queryFinishTime = System.currentTimeMillis();
                    log.info("数据第{}页查询组装完成，耗时：{}ms", curPage, (queryFinishTime - excelFinishTime));

                    excelWriter.write(exportDataList, writeSheet);
                    // 关闭流
                    excelWriter.finish();
                    log.info("数据第{}页导出完成，耗时：{}ms", curPage, (System.currentTimeMillis() - queryFinishTime));
                } catch (Exception e) {
                    log.error("导出账户明细第" + curPage + "页异常", e);
                    if (excelWriter != null) {
                        excelWriter.finish();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        try {
            // 等待各个线程执行完成
            countDownLatch.await();
            long countDownLatchTime = System.currentTimeMillis();
            log.info("接口进入到各个线程执行完成总耗时：{}ms", countDownLatchTime - inTime);

            // 导出完成，加压缩
            ZipUtil.zip(folderPath);
            long zipTime = System.currentTimeMillis();
            log.info("压缩耗时：{}ms", zipTime - countDownLatchTime);

            // 删除原文件夹
            String zipPath = tempFolderPath + today + ".zip";
            if (FileUtil.exist(zipPath)) {
                FileUtil.del(folderPath);
            }
            long deleteTime = System.currentTimeMillis();
            log.info("删除文件耗时：{}ms", deleteTime - zipTime);

            // 向客户端输出文件并清除本地文件
            String contentType = "application/octet-stream";
            String encode = URLEncoder.encode(downloadFileName, "utf-8");
            String contentDisposition = "attachment;filename=" + encode + "_" + today + ".zip";
            IOTool.outStreamWrite(response, zipPath, contentType, contentDisposition);
        } finally {
            // 导出完成后执行情况改为false，下一次导出可使用
            exportExcuteConcurrentMap.put(exportKind, Boolean.FALSE);
        }
    }

}
